using System;
using System.Xml.Serialization;

namespace SharpMud.MudLib.SAMPLE.Environment.BuiltIn
{
	/// <summary>
	/// Defines a sample hardcoded area, TheMaze. Used for testing purposes.
	/// </summary>
	[XmlRoot("TheMaze")]
	public class TheMaze
	{
		public enum SimpleDirection
		{
			North	= ExitDirection.North,
			South	= ExitDirection.South,
			East	= ExitDirection.East,
			West	= ExitDirection.West
		}
		
		public static Area Create(int width, int height)
		{
			Area a = new Area();
		
			a.Name = "TheMaze";
			a.Author = "None";

			RoomPrototype[,] rooms = CreateRooms(width,height);
			MutateIntoMaze(ref rooms);
			DescribeRooms(ref rooms);

			foreach(RoomPrototype r in rooms)
			{
				a.Rooms = new Room[] {r.Room};
			}

			return a;
		}

		#region Constructors
		private TheMaze()
			:base()
		{

		}
		#endregion

		private static void DescribeRooms(ref RoomPrototype[,] rooms)
		{
			int width  = rooms.GetUpperBound(0) - rooms.GetLowerBound(0);
			int height = rooms.GetUpperBound(1) - rooms.GetLowerBound(1);
			System.Random r = new Random();
				foreach(RoomPrototype cr in rooms)
				{
					int wallCount = (4-cr.Room.ExitCollection.Count);
					string strWallCount = "";
					if(wallCount == 1) strWallCount = "only one side.";
					if(wallCount == 2) strWallCount = "two sides.";
					if(wallCount == 3) strWallCount = "three sides, you can barely breath!";
					if(wallCount == 4) strWallCount = "on all four sides! Your trapped!";

					string sExtra = "";
					int v = r.Next(0,9);
					v = r.Next(0,9);
					if(v>2)
						sExtra = sExtra + "The walls are made of a strange stone which glitters in the low light. ";
					if(v>4)
						sExtra = sExtra + "The floor is made of the same material. ";
					v = r.Next(0,9);
					if(v>7)
						sExtra = sExtra + "You barely notice the sound of water dripping in the wall nearby. ";
					v = r.Next(0,9);
					if(v>4)
						sExtra = sExtra + "The stench of rot is overwhelming! ";
					v = r.Next(0,9);
					if(v>2)
						sExtra = sExtra + "You think you see an 'Exit' sign in the distance, but it might be your imagination... ";
					
					cr.Room.ShortDescription = 
						String.Format("A Dark Maze [white]^[[yellow]{0}[white],[yellow]{1}[white]^]",cr.X,cr.Y);
					cr.Room.LongDescription = String.Format(
						"You are in a dark labyrinth. Walls surround you on {0} "
					+	sExtra
					+	"Somehow you know where you are...but can you find your way out?" 
						,strWallCount
						);
			}
		}

		private static void MutateIntoMaze(ref RoomPrototype[,] rooms)
		{
			int width  = rooms.GetUpperBound(0) - rooms.GetLowerBound(0);
			int height = rooms.GetUpperBound(1) - rooms.GetLowerBound(1);
			
			//Create a cell stack
			System.Collections.IStack<RoomPrototype> cellStack = new System.Collections.GenericStack<RoomPrototype>();
			
			//Get total cell count
			int totalCells = width * height;
			//Init visited cells count
			int visitedCells = 1;
			//Find the center cell
			RoomPrototype centerCell = rooms[(width-1)/2,(height-1)/2];
			RoomPrototype upperLeftCell = rooms[0,0];
			RoomPrototype lowerRightCell = rooms[width-1,height-1];
			//Set the current cell to the center cell
			RoomPrototype currentCell = upperLeftCell;

			RoomPrototype nextRoom=null;
			SimpleDirection nextRoomDir=(SimpleDirection)ExitDirection.Northeast;

			//While visitedCells < totalCells ...
			while(visitedCells < totalCells)
			{
				//Find neigbors with no exits
				System.Collections.ArrayList found = new System.Collections.ArrayList();
				foreach(RoomPrototype rn in GetNeighbors(rooms,currentCell.X,currentCell.Y))
				{
					System.Diagnostics.Debug.Assert(rn!=currentCell);
					if(rn.Room.ExitCollection.Count==0)
						found.Add(rn);
				}

				//If one or more found
				if(found.Count>0)
				{
					nextRoom = (RoomPrototype)ChooseRandom((object[])found.ToArray());
					nextRoomDir = GetDirectionBetween(currentCell,nextRoom);
					//At this point we have nextRoom and nextRoomDir
                    
					//Connect currentCell (room) and nextRoom to each other
					System.Diagnostics.Debug.Assert(currentCell!=nextRoom);
					currentCell.Room.ExitCollection.Add(new Environment.SimpleDirectionalExit((ExitDirection)nextRoomDir,nextRoom.Room.RoomID));
					nextRoom.Room.ExitCollection.Add(new Environment.SimpleDirectionalExit(ReverseExitDirectionAttribute.GetReverseExitDirection((ExitDirection)nextRoomDir),currentCell.Room.RoomID));

					//push CurrentCell location on the CellStack  
					cellStack.Push(currentCell);
					
					//make the new cell CurrentCell  
					currentCell = nextRoom;
					
					//add 1 to VisitedCells 
					visitedCells=visitedCells+1;
					
				}
				else
				{
					//Set the currentCell to a cell popped off of the cell stack
					currentCell = (RoomPrototype)cellStack.Pop();
				}
			}
		}

		private static RoomPrototype GetNeighbor(RoomPrototype[,] rooms, int curX, int curY, SimpleDirection direction)
		{
			if(
				(
				(curX <=  (rooms.GetLowerBound(0)  ) && direction==SimpleDirection.West)
				||	(curX >= (rooms.GetUpperBound(0)  ) && direction==SimpleDirection.East)
				||	(curY <=  (rooms.GetLowerBound(1)  ) && direction==SimpleDirection.North)
				||	(curY >= (rooms.GetUpperBound(1)  ) && direction==SimpleDirection.South)
				)
				)
			{
				throw new InvalidOperationException();
			}

            if(direction==SimpleDirection.East)				
				return rooms[curX+1,curY];
			if(direction==SimpleDirection.North)				
				return rooms[curX,curY-1];
			if(direction==SimpleDirection.South)				
				return rooms[curX,curY+1];
			if(direction==SimpleDirection.West)				
				return rooms[curX-1,curY];

			throw new ArgumentOutOfRangeException("direction",direction,"Unrecognized value.");
		}

		private static RoomPrototype[] GetNeighbors(RoomPrototype[,] rooms, int curX, int curY)
		{
			System.Collections.ArrayList al = new System.Collections.ArrayList();
			foreach(SimpleDirection direction in Enum.GetValues(typeof(SimpleDirection)))
			{
				try
				{
					al.Add(GetNeighbor(rooms,curX,curY,direction));
				}
				catch
				{
					System.Diagnostics.Debug.Assert(
					(
					(curX <=  (rooms.GetLowerBound(0)  ) && direction==SimpleDirection.West)
				||	(curX >= (rooms.GetUpperBound(0)  ) && direction==SimpleDirection.East)
				||	(curY <=  (rooms.GetLowerBound(1)  ) && direction==SimpleDirection.North)
				||	(curY >= (rooms.GetUpperBound(1)  ) && direction==SimpleDirection.South)
					)
						);
				}
			}
			return (RoomPrototype[])al.ToArray(typeof(RoomPrototype));
		}

		private static RoomPrototype[,] CreateRooms(int width, int height)
		{
			//Initialize rooms
			RoomPrototype[,] rooms = new RoomPrototype[width,height];
			for(int x=0;x<width;x++)
			{
				for(int y=0;y<height;y++)
				{
					rooms[x,y] = new RoomPrototype(x,y);
					System.Random r = new System.Random();
					int v = 42 + ((x*height)+y);
					rooms[x,y].Room.RoomID = new RoomID(v);
					rooms[x,y].Room.LongDescription = "";
					rooms[x,y].Room.ShortDescription= "";
				}
			}
			return rooms;
		}
		
		private static SimpleDirection GetDirectionBetween(RoomPrototype origin, RoomPrototype dest)
		{
			int xDiff = dest.X - origin.X;
			int yDiff = dest.Y - origin.Y;
			if(Math.Abs(xDiff)>1) //Not a neigbor
				throw new InvalidOperationException();
			if(Math.Abs(yDiff)>1) //Not a neigbor
				throw new InvalidOperationException();
			if((xDiff!=0) && (yDiff!=0)) //A funny direction that isn't supported - NE, SE, NW, SW
				throw new InvalidOperationException();
			if((xDiff==0) && (yDiff==0)) //No location difference
				throw new InvalidOperationException();

			     if((xDiff == -1) && (yDiff == 0))
					return SimpleDirection.West;
			else if((xDiff == 0))
			{
				if(yDiff==-1)
				{
					return SimpleDirection.North;
				}
				if(yDiff==1)
				{
					return SimpleDirection.South;
				}
			}
			else if((xDiff==+1) && (yDiff==0))
				return SimpleDirection.East;
			else
			{
				throw new InvalidOperationException();
			}
			throw new InvalidOperationException();
		}

		private static System.Random ___rand;
		private static object ChooseRandom(object[] objectList)
		{
			if(___rand==null)
			{
				___rand = new System.Random();
			}
			return objectList[ (int)((double)objectList.Length * (double)___rand.NextDouble()) ];
		}

		private class RoomPrototype
		{
			private int _X;
			public int X {get{return _X;}}

			private int _Y;
			public int Y {get{return _Y;}}


			public Room Room;

			private RoomPrototype()
			{
				Room = new Room();
			}
			public RoomPrototype(int x, int y)
				:this()
			{
				_X = x;
				_Y = y;
			}
		}
	}
}